﻿// Copyright © 2010 Silvestru Iapascurta.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see license.txt for details.
// All other rights reserved.

using System;
using System.ComponentModel;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
using FeedDotNet;
using FeedDotNet.Common;

namespace Karoro
{
    [ComVisible(true)]
    public partial class KaroroForm : Form
    {
        public KaroroForm()
        {
            InitializeComponent();

            extendedWebBrowser1.StatusTextChanged += new System.EventHandler(this.extendedWebBrowser1_StatusTextChanged);
            FeedUpdated += new System.EventHandler<TreeViewEventArgs>(this.OnFeedUpdated);
            extendedWebBrowser1.ObjectForScripting = this;

            for (int i = 0; i < Properties.Settings.Default.maxConnections; i++)
            {
                BackgroundWorker feed_worker = new BackgroundWorker();
                feed_worker.DoWork += new DoWorkEventHandler(feed_worker_DoWork);
                feed_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(feed_worker_RunWorkerCompleted);
                feed_workers.Add(feed_worker);
            }

            LoadSettings();
        }

        public static bool CurrentStyle(Style style)
        {
            if (style.Name == Properties.Settings.Default.style) return true;
            else return false;
        }

        public static bool DefaultStyle(Style style)
        {
            if (style.Name == "Unstyled") return true;
            else return false;
        }

        public void NavigatePage(int requestedItems, int startIndex)
        {
            FileInfo temp = CreateTemporaryHtml(feedTree.SelectedNode, requestedItems, startIndex);

            extendedWebBrowser1.Navigate(temp.FullName);
        }

        public void SaveItem(int item_id)
        {
            try
            {
                Database.Execute("INSERT INTO saved SELECT * FROM items WHERE item_id = @item_id", new SQLiteParameter[] { new SQLiteParameter("@item_id", item_id) });
            }
            catch (SQLiteException ex)
            {
                if (ex.ErrorCode == SQLiteErrorCode.Constraint)
                    MessageBox.Show("This item has already been saved.");
                else
                    MessageBox.Show(ex.Message);
            }
        }

        private void feed_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                FeedWorkerArgs result = e.Result as FeedWorkerArgs;
                ProcessFeedToNode(result.Feed, result.Action, result.Node);

                toolStripProgressBar1.PerformStep();
                available_workers++;

                if (result != null)
                {
                    uniques.Remove(((FeedWorkerArgs)e.Result).Uri);
                }

                if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum)
                {
                    toolStripProgressBar1.Visible = false;
                }

                StartWork();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                throw ex;
            }
        }

        private void AssignWork(FeedWorkerArgs arg)
        {
            if (!uniques.Contains(arg.Uri))
            {
                feed_queue.Enqueue(arg);
                uniques.Add(arg.Uri);
                toolStripProgressBar1.Maximum += 1;
                StartWork();
            }
            else
            {
                Debug.WriteLine(String.Format("{0} is already in the queue!", arg.Uri));
            }
        }

        private void StartWork()
        {
            foreach (BackgroundWorker x in feed_workers)
            {
                if (!x.IsBusy && feed_queue.Count != 0 && available_workers != 0)
                {
                    x.RunWorkerAsync(feed_queue.Dequeue());
                    available_workers--;
                                        
                    if (toolStripProgressBar1.Visible == false)
                        toolStripProgressBar1.Visible = true;
                }
            }
        }

        private void feed_worker_DoWork(object sender, DoWorkEventArgs e)
        {
            FeedWorkerArgs arg = (FeedWorkerArgs)e.Argument;

            FeedWorkerArgs result = new FeedWorkerArgs();
            Feed x = FeedReader.Read(arg.Uri);

            if (x != null)
            {
                result.Feed = x;
                result.Action = arg.Action;
                result.Node = arg.Node;
                result.Uri = x.XmlUri.Uri;
            }

            e.Result = result;
        }

        private void AddFeedDialog_Add(object sender, FeedEventArgs e)
        {
            if (e.Feed != null)
            {
                ProcessFeedToNode(e.Feed, SubAction.Add, null);
            }
            else
            {
                foreach (string link in e.Links)
                {
                    FeedWorkerArgs t = new FeedWorkerArgs(link, SubAction.Add, null);
                    AssignWork(t);
                }
            }
        }

        private void OnPreferencesChanged(object sender, EventArgs e)
        {
            extendedWebBrowser1.Stop();
            CleanTempFiles();
            ReselectCurrentNode();
        }

        private void ProcessFeedToNode(Feed feed, SubAction action, TreeNode destinationNode)
        {
            if (feed == null)
                return;

            if (feed.Items.Count == 0)
                return;

            // try block here, possible exceptions would be malformed/incomplete feeds
            string msg = null;

            if (action == SubAction.Add)
            {
                try
                {
                    TreeNode node = new TreeNode(feed.Title);                    
                    node.Name = feed.Title;

                    if (destinationNode != null)
                    {
                        string[] tag_string = ((string)destinationNode.Tag).Split(';');
                        node.Tag = String.Format("feed;{0}", Database.AddFeed(feed, tag_string[1]));
                        feedTree.Nodes[feedTree.Nodes.IndexOf(destinationNode)].Nodes.Add(node);
                    }
                    else
                    {
                        node.Tag = String.Format("feed;{0}", Database.AddFeed(feed, null));
                        feedTree.Nodes.Add(node);
                    }

                    msg = String.Format("Added \"{0}\".", feed.Title);
                }
                catch (SQLiteException ex)
                {
                    if (ex.ErrorCode == SQLiteErrorCode.Constraint)
                        msg = String.Format("Feed tree already contains \"{0}\"", feed.Title);
                    else
                        MessageBox.Show((string)ex.Message, "SQLite Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }

            if (action == SubAction.Update && ContainsNode(feedTree.Nodes, destinationNode))
            {
                string[] tag_string = destinationNode.Tag.ToString().Split(';');
                int uitems = Database.UpdateFeed(int.Parse(tag_string[1]), feed);
                FeedUpdated(this, new TreeViewEventArgs(destinationNode));
                msg = String.Format("Updated \"{0}\" with {1} items", destinationNode.Text, uitems);
            }

            karoroStatus.Text = msg;
            Debug.WriteLine(msg, "Processing");
        }

        private void LoadSettings()
        {
            // window size/menus/checkboxes
            this.Location = Properties.Settings.Default.location;
            
            if (this.WindowState == FormWindowState.Normal)
            {
                if (Properties.Settings.Default.size != new Size(0, 0))
                {
                    this.Size = Properties.Settings.Default.size;
                }
                else
                {
                    this.Size = this.DefaultSize;
                }
            }

            menuStrip1.Visible = menuBarToolStripMenuItem.Checked = Properties.Settings.Default.menuBar;
            statusStrip1.Visible = statusBarToolStripMenuItem.Checked = Properties.Settings.Default.statusBar;

            splitContainer1.SplitterDistance = Properties.Settings.Default.sideBarWidth;
            
            switch (Properties.Settings.Default.sortByView)
            {
                case 0:
                    sourceToolStripMenuItem.Checked = true;
                    break;
                case 1:
                    dateToolStripMenuItem.Checked = true;
                    break;
                default:
                    break;
            }

            switch (Properties.Settings.Default.timespanView)
            {
                case 0:
                    hoursToolStripMenuItem.Checked = true;
                    break;
                case 1:
                    hoursToolStripMenuItem1.Checked = true;
                    break;
                case 2:
                    weekToolStripMenuItem.Checked = true;
                    break;
                case 3:
                    monthToolStripMenuItem.Checked = true;
                    break;
                case 4:
                    anytimeToolStripMenuItem.Checked = true;
                    break;
                default:
                    break;
            }

            if (!stylesPath.Exists)
            {
                stylesPath.Create();
                DirectoryInfo defaultPath = stylesPath.CreateSubdirectory("Default.style");
                FileInfo template = new FileInfo(System.IO.Path.Combine(defaultPath.FullName, "template.html"));
                FileInfo stylesheet = new FileInfo(System.IO.Path.Combine(defaultPath.FullName, "stylesheet.css"));

                /* *
                using (TextWriter textWriter = template.CreateText())
                {
                    textWriter.Write(Properties.Resources.template);
                }

                using (TextWriter textWriter = stylesheet.CreateText())
                {
                    textWriter.Write(Properties.Resources.stylesheet);
                }
                 * */
            }

            PopulateTree();

            string[] selectedNode = Properties.Settings.Default.lastSelectedNode.Split(new string[] { "\\" }, 2, StringSplitOptions.RemoveEmptyEntries);
            if (selectedNode.Length == 1)
                feedTree.SelectedNode = feedTree.Nodes[selectedNode[0]];
            else if (selectedNode.Length == 2)
                feedTree.SelectedNode = feedTree.Nodes[selectedNode[0]].Nodes[selectedNode[1]];

            // get styles
            foreach (DirectoryInfo dir in stylesPath.GetDirectories("*.style"))
            {
                foreach (FileInfo file in dir.GetFiles())
                {
                    if (file.Name == "template.html")
                    {
                        Style s = new Style(dir);
                        if (s.IsValid)
                        {
                            Themes.Add(s);
                        }
                    }
                }
            }
        }

        private void SaveSettings()
        {
            // window settings
            Properties.Settings.Default.menuBar = menuStrip1.Visible;
            Properties.Settings.Default.statusBar = statusStrip1.Visible;
            Properties.Settings.Default.sideBar = splitContainer1.Panel1Collapsed;
            Properties.Settings.Default.sideBarWidth = splitContainer1.SplitterDistance;
            Properties.Settings.Default.Save();
        }

        private void Update(TreeNode node)
        {
            string[] tag_string = node.Tag.ToString().Split(';');

            if (tag_string[0] == "feed")
            {
                string xml_url = Database.GetUpdateUrl(int.Parse(tag_string[1]));
                FeedWorkerArgs t = new FeedWorkerArgs(xml_url, SubAction.Update, node);
                AssignWork(t);
            }

            if (tag_string[0] == "folder")
            {
                foreach (TreeNode t in node.Nodes)
                {
                    Update(t);
                }
            }
        }

        private void UpdateAll()
        {
            foreach (TreeNode node in feedTree.Nodes)
            {
                Update(node);
            }
        }

        private bool ContainsNode(TreeNodeCollection tnc, TreeNode requestedNode)
        {
            if (tnc.Contains(requestedNode))
                return true;

            foreach (TreeNode node in tnc)
            {
                if (node.Nodes.Contains(requestedNode))
                    return true;

                if (ContainsNode(node.Nodes, requestedNode))
                    return true;
            }

            return false;
        }

        private bool ContainsFeed(TreeNodeCollection tnc, string xmlUri)
        {
            foreach (TreeNode node in tnc)
            {
                string[] tag_string = node.Tag.ToString().Split(';');
            }

            return false;
        }

        private void ReselectCurrentNode()
        {
            TreeNode tempNode = feedTree.SelectedNode;
            feedTree.SelectedNode = null;
            feedTree.SelectedNode = tempNode;
        }

        private void OnFeedUpdated(object sender, TreeViewEventArgs e)
        {
            if (feedTree.SelectedNode == e.Node || feedTree.SelectedNode == e.Node.Parent)
                ReselectCurrentNode();
        }

        private FileInfo CreateTemporaryHtml(TreeNode node, int requestedItems, int startIndex)
        {
            FileInfo temp = null;
            long tag = 0;

            string[] tag_string = node.Tag.ToString().Split(';');

            if (tag_string[0] == "feed")
                tag = Database.ExecuteQuery(String.Format("SELECT MAX(item_id) FROM items WHERE feed_id = {0}", tag_string[1]));

            if (tag_string[0] == "folder")
                tag = Database.ExecuteQuery(String.Format("SELECT MAX(item_id) FROM items, feeds WHERE items.feed_id = feeds.feed_id AND feeds.folder_id = {0}", tag_string[1]));

            temp = new FileInfo(System.IO.Path.Combine(Path.GetTempPath(),
                String.Format("{0}{1}_r{2}s{3}_{4}.html", tag_string[0], tag_string[1], requestedItems, startIndex, tag))
                );

            if (!temp.Exists)
            {
                tempFiles.Add(temp);
                using (TextWriter textWriter = temp.CreateText())
                {
                    textWriter.Write(Formatting.GenerateHtmlView(node, this, startIndex));
                }
            }

            return temp;
        }

        private void CleanTempFiles()
        {
            foreach (FileInfo file in tempFiles)
            {
                file.Delete();
            }
        }

        private void PopulateTree()
        {
            DataTable data = Database.Query(
                "SELECT folders.title AS folder_title, feeds.title AS feed_title, feeds.feed_id, feeds.update_interval, folders.folder_id " +
                    "FROM folders " +
                    "LEFT JOIN feeds ON (feeds.folder_id = folders.folder_id) " +
                    "ORDER BY feeds.folder_id DESC");

            foreach (DataRow row in data.Rows)
            {
                if ((string)row["folder_title"] == "Uncategorized" && !(row["feed_id"] is DBNull))
                {
                    TreeNode t = new TreeNode((string)row["feed_title"]);
                    t.Tag = String.Format("feed;{0}", row["feed_id"]);
                    t.Name = (string)row["feed_title"];
                    feedTree.Nodes.Add(t);
                    CreateTimer((int)(long)row["update_interval"], t);
                }
                else if ((string)row["folder_title"] != "Uncategorized")
                {
                    if (feedTree.Nodes.ContainsKey((string)row["folder_title"]) && !(row["feed_id"] is DBNull))
                    {
                        TreeNode t = new TreeNode((string)row["feed_title"]);
                        t.Tag = String.Format("feed;{0}", row["feed_id"]);
                        t.Name = (string)row["feed_title"];
                        feedTree.Nodes[(string)row["folder_title"]].Nodes.Add(t);
                        CreateTimer((int)(long)row["update_interval"], t);
                    }
                    else
                    {
                        TreeNode folder = new TreeNode((string)row["folder_title"]);
                        folder.Tag = String.Format("folder;{0}", row["folder_id"]);
                        folder.Name = (string)row["folder_title"];
                        feedTree.Nodes.Add(folder);
                        if (!(row["feed_id"] is DBNull))
                        {
                            TreeNode t = new TreeNode((string)row["feed_title"]);
                            t.Tag = String.Format("feed;{0}", row["feed_id"]);
                            t.Name = (string)row["feed_title"];
                            feedTree.Nodes[(string)row["folder_title"]].Nodes.Add(t);
                            CreateTimer((int)(long)row["update_interval"], t);
                        }
                    }
                }
            }
        }

        private void feedTimer_Tick(object sender, EventArgs e)
        {
            Timer sz = (Timer)sender;
            Update((TreeNode)sz.Tag);
        }

        private void CreateTimer(int interval, object tag)
        {
            if (interval > 0)
            {
                Timer feedTimer = new Timer();
                feedTimer.Interval = interval * 60 * 1000;
                feedTimer.Tag = tag;
                feedTimer.Tick += new EventHandler(feedTimer_Tick);
                feedTimer.Start();
            }
        }

        private void propertiesItem1_Click(object sender, EventArgs e)
        {
            string[] tag_string = ((string)feedTree.SelectedNode.Tag).Split(';');

            if (tag_string[0] == "feed")
            {
                FeedPreferencesDialog dialog = new FeedPreferencesDialog();
                dialog.Data = Database.Query(String.Format("SELECT * FROM feeds WHERE feed_id = {0}", tag_string[1]));
                dialog.Show();
            }
        }
    }
}
